"""
实时库存优化系统
实现动态补货、库存预警和实时库存优化功能
"""

import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import json
from typing import Dict, List, Tuple, Optional
import warnings
warnings.filterwarnings('ignore')

class InventoryOptimizer:
    """库存优化器"""
    
    def __init__(self):
        self.safety_stock_factors = {
            'A': 1.65,  # 95%服务水平
            'B': 1.28,  # 90%服务水平
            'C': 1.04   # 85%服务水平
        }
        self.lead_times = {}
        self.demand_forecasts = {}
        self.service_levels = {}
        
    def calculate_abc_classification(self, df: pd.DataFrame) -> pd.DataFrame:
        """计算ABC分类"""
        # 计算年销售额
        sales_analysis = df.groupby('product_id').agg({
            'quantity': 'sum',
            'price': 'mean'
        }).reset_index()
        sales_analysis['annual_revenue'] = sales_analysis['quantity'] * sales_analysis['price']
        
        # 按销售额排序
        sales_analysis = sales_analysis.sort_values('annual_revenue', ascending=False)
        
        # 计算累计百分比
        sales_analysis['cumulative_revenue'] = sales_analysis['annual_revenue'].cumsum()
        sales_analysis['total_revenue'] = sales_analysis['annual_revenue'].sum()
        sales_analysis['cumulative_percentage'] = (sales_analysis['cumulative_revenue'] / 
                                                  sales_analysis['total_revenue'] * 100)
        
        # ABC分类
        def classify_abc(percentage):
            if percentage <= 80:
                return 'A'
            elif percentage <= 95:
                return 'B'
            else:
                return 'C'
        
        sales_analysis['abc_class'] = sales_analysis['cumulative_percentage'].apply(classify_abc)
        
        return sales_analysis[['product_id', 'abc_class', 'annual_revenue']]
    
    def calculate_eoq(self, demand: float, ordering_cost: float, holding_cost: float) -> float:
        """计算经济订货量(EOQ)"""
        if demand <= 0 or holding_cost <= 0:
            return 0
        return np.sqrt(2 * demand * ordering_cost / holding_cost)
    
    def calculate_safety_stock(self, demand_std: float, lead_time: float, 
                             service_level: str = 'A') -> float:
        """计算安全库存"""
        z_score = self.safety_stock_factors.get(service_level, 1.65)
        return z_score * demand_std * np.sqrt(lead_time)
    
    def calculate_reorder_point(self, avg_demand: float, lead_time: float, 
                              safety_stock: float) -> float:
        """计算再订货点"""
        return avg_demand * lead_time + safety_stock
    
    def analyze_demand_patterns(self, df: pd.DataFrame, product_id: str) -> Dict:
        """分析需求模式"""
        product_data = df[df['product_id'] == product_id].copy()
        
        if len(product_data) < 30:
            return {'error': 'Insufficient data'}
        
        # 计算统计指标
        daily_demand = product_data.groupby('date')['quantity'].sum()
        
        stats = {
            'mean_demand': daily_demand.mean(),
            'demand_std': daily_demand.std(),
            'cv': daily_demand.std() / daily_demand.mean() if daily_demand.mean() > 0 else 0,
            'trend': self._calculate_trend(daily_demand),
            'seasonality': self._detect_seasonality(daily_demand),
            'recent_demand': daily_demand.tail(7).mean(),
            'demand_growth': self._calculate_growth_rate(daily_demand)
        }
        
        return stats
    
    def _calculate_trend(self, series: pd.Series) -> float:
        """计算需求趋势"""
        x = np.arange(len(series))
        y = series.values
        coeffs = np.polyfit(x, y, 1)
        return coeffs[0]  # 斜率
    
    def _detect_seasonality(self, series: pd.Series) -> float:
        """检测季节性"""
        # 简单的季节性检测
        if len(series) < 365:
            return 0.0
        
        # 计算7天滚动平均
        rolling_avg = series.rolling(window=7).mean()
        
        # 计算季节性强度
        seasonal_strength = abs(rolling_avg.std() / rolling_avg.mean()) if rolling_avg.mean() > 0 else 0
        return seasonal_strength
    
    def _calculate_growth_rate(self, series: pd.Series) -> float:
        """计算增长率"""
        if len(series) < 30:
            return 0.0
        
        recent = series.tail(7).mean()
        previous = series.tail(30).head(7).mean()
        
        if previous > 0:
            return (recent - previous) / previous
        return 0.0

class DynamicReplenishmentSystem:
    """动态补货系统"""
    
    def __init__(self, inventory_optimizer: InventoryOptimizer):
        self.optimizer = inventory_optimizer
        self.replenishment_rules = {}
        self.alert_thresholds = {
            'critical': 0.1,   # 库存低于10%触发紧急补货
            'warning': 0.3,    # 库存低于30%触发预警
            'normal': 0.5      # 库存低于50%触发常规补货
        }
    
    def generate_replenishment_plan(self, inventory_df: pd.DataFrame, 
                                  demand_df: pd.DataFrame, 
                                  lead_times: Dict[str, float]) -> pd.DataFrame:
        """生成补货计划"""
        
        # 合并库存和需求数据
        latest_inventory = inventory_df.groupby('product_id').agg({
            'current_stock': 'last',
            'price': 'mean'
        }).reset_index()
        
        # ABC分类
        abc_df = self.optimizer.calculate_abc_classification(demand_df)
        
        # 合并数据
        merged_df = latest_inventory.merge(abc_df, on='product_id', how='left')
        
        replenishment_plan = []
        
        for _, row in merged_df.iterrows():
            product_id = row['product_id']
            current_stock = row['current_stock']
            abc_class = row['abc_class']
            
            # 分析需求模式
            demand_stats = self.optimizer.analyze_demand_patterns(demand_df, product_id)
            
            if 'error' in demand_stats:
                continue
            
            # 获取前置时间
            lead_time = lead_times.get(product_id, 7)  # 默认7天
            
            # 计算补货参数
            daily_demand = demand_stats['mean_demand']
            demand_std = demand_stats['demand_std']
            
            # 计算经济订货量
            annual_demand = daily_demand * 365
            ordering_cost = 50  # 假设订货成本
            holding_cost_rate = 0.25  # 假设库存持有成本率
            unit_cost = row['price']
            holding_cost = unit_cost * holding_cost_rate
            
            eoq = self.optimizer.calculate_eoq(annual_demand, ordering_cost, holding_cost)
            
            # 计算安全库存
            safety_stock = self.optimizer.calculate_safety_stock(
                demand_std, lead_time, abc_class
            )
            
            # 计算再订货点
            reorder_point = self.optimizer.calculate_reorder_point(
                daily_demand, lead_time, safety_stock
            )
            
            # 计算建议补货量
            if current_stock <= reorder_point:
                suggested_order = max(eoq, reorder_point + eoq - current_stock)
                urgency_level = self._determine_urgency(current_stock, reorder_point)
            else:
                suggested_order = 0
                urgency_level = 'none'
            
            replenishment_plan.append({
                'product_id': product_id,
                'current_stock': current_stock,
                'abc_class': abc_class,
                'daily_demand': daily_demand,
                'reorder_point': reorder_point,
                'safety_stock': safety_stock,
                'suggested_order': suggested_order,
                'urgency_level': urgency_level,
                'expected_delivery_date': datetime.now() + timedelta(days=lead_time),
                'total_cost': suggested_order * unit_cost,
                'stockout_risk': self._calculate_stockout_risk(current_stock, reorder_point)
            })
        
        return pd.DataFrame(replenishment_plan)
    
    def _determine_urgency(self, current_stock: float, reorder_point: float) -> str:
        """确定紧急程度"""
        if current_stock <= reorder_point * self.alert_thresholds['critical']:
            return 'critical'
        elif current_stock <= reorder_point * self.alert_thresholds['warning']:
            return 'warning'
        elif current_stock <= reorder_point * self.alert_thresholds['normal']:
            return 'normal'
        else:
            return 'none'
    
    def _calculate_stockout_risk(self, current_stock: float, reorder_point: float) -> float:
        """计算缺货风险"""
        if current_stock <= 0:
            return 1.0
        elif current_stock <= reorder_point * 0.5:
            return 0.8
        elif current_stock <= reorder_point:
            return 0.5
        else:
            return 0.1

class RealTimeInventoryMonitor:
    """实时库存监控器"""
    
    def __init__(self):
        self.alerts = []
        self.monitoring_rules = {}
        
    def monitor_inventory_levels(self, inventory_df: pd.DataFrame, 
                               replenishment_plan: pd.DataFrame) -> Dict:
        """监控库存水平"""
        
        # 合并当前库存和补货计划
        current_status = inventory_df.merge(
            replenishment_plan[['product_id', 'reorder_point', 'safety_stock']], 
            on='product_id', 
            how='left'
        )
        
        alerts = {
            'critical': [],
            'warning': [],
            'info': []
        }
        
        for _, row in current_status.iterrows():
            product_id = row['product_id']
            current_stock = row['current_stock']
            reorder_point = row.get('reorder_point', 0)
            safety_stock = row.get('safety_stock', 0)
            
            # 计算库存状态
            if reorder_point > 0:
                stock_ratio = current_stock / reorder_point
            else:
                stock_ratio = 1.0
            
            # 生成警报
            if stock_ratio <= 0.1:
                alerts['critical'].append({
                    'product_id': product_id,
                    'current_stock': current_stock,
                    'reorder_point': reorder_point,
                    'alert_type': 'critical_stockout',
                    'message': f'产品 {product_id} 库存严重不足，需要紧急补货',
                    'timestamp': datetime.now()
                })
            elif stock_ratio <= 0.3:
                alerts['warning'].append({
                    'product_id': product_id,
                    'current_stock': current_stock,
                    'reorder_point': reorder_point,
                    'alert_type': 'low_stock',
                    'message': f'产品 {product_id} 库存偏低，建议补货',
                    'timestamp': datetime.now()
                })
            elif stock_ratio <= 0.5:
                alerts['info'].append({
                    'product_id': product_id,
                    'current_stock': current_stock,
                    'reorder_point': reorder_point,
                    'alert_type': 'monitoring',
                    'message': f'产品 {product_id} 需要关注库存水平',
                    'timestamp': datetime.now()
                })
        
        return alerts
    
    def generate_inventory_report(self, inventory_df: pd.DataFrame, 
                                replenishment_plan: pd.DataFrame) -> Dict:
        """生成库存报告"""
        
        # 合并数据
        merged_df = inventory_df.merge(
            replenishment_plan, 
            on='product_id', 
            how='left',
            suffixes=('_current', '_plan')
        )
        
        # 计算关键指标
        total_products = len(merged_df)
        low_stock_products = len(merged_df[merged_df['current_stock'] <= merged_df['reorder_point']])
        critical_products = len(merged_df[merged_df['current_stock'] <= merged_df['reorder_point'] * 0.1])
        
        total_inventory_value = (merged_df['current_stock'] * merged_df['price']).sum()
        total_suggested_order_value = (merged_df['suggested_order'] * merged_df['price']).sum()
        
        # 按ABC分类统计
        abc_stats = merged_df.groupby('abc_class').agg({
            'product_id': 'count',
            'current_stock': 'sum',
            'suggested_order': 'sum'
        }).reset_index()
        
        report = {
            'summary': {
                'total_products': total_products,
                'low_stock_products': low_stock_products,
                'critical_products': critical_products,
                'inventory_health_score': max(0, 100 - (critical_products * 10)),
                'total_inventory_value': total_inventory_value,
                'total_replenishment_value': total_suggested_order_value
            },
            'abc_analysis': abc_stats.to_dict('records'),
            'alerts': self.monitor_inventory_levels(inventory_df, replenishment_plan),
            'timestamp': datetime.now()
        }
        
        return report

class InventoryOptimizationEngine:
    """库存优化引擎主类"""
    
    def __init__(self):
        self.optimizer = InventoryOptimizer()
        self.replenishment_system = DynamicReplenishmentSystem(self.optimizer)
        self.monitor = RealTimeInventoryMonitor()
        
    def run_optimization_cycle(self, inventory_df: pd.DataFrame, 
                             demand_df: pd.DataFrame,
                             lead_times: Dict[str, float]) -> Dict:
        """运行完整的库存优化周期"""
        
        print("正在生成补货计划...")
        replenishment_plan = self.replenishment_system.generate_replenishment_plan(
            inventory_df, demand_df, lead_times
        )
        
        print("正在监控库存水平...")
        alerts = self.monitor.monitor_inventory_levels(inventory_df, replenishment_plan)
        
        print("正在生成库存报告...")
        inventory_report = self.monitor.generate_inventory_report(
            inventory_df, replenishment_plan
        )
        
        return {
            'replenishment_plan': replenishment_plan,
            'alerts': alerts,
            'inventory_report': inventory_report,
            'optimization_timestamp': datetime.now()
        }
    
    def save_optimization_results(self, results: Dict, filepath: str):
        """保存优化结果"""
        # 将DataFrame转换为字典
        if 'replenishment_plan' in results:
            results['replenishment_plan'] = results['replenishment_plan'].to_dict('records')
        
        # 处理datetime对象
        def convert_datetime(obj):
            if isinstance(obj, datetime):
                return obj.isoformat()
            elif isinstance(obj, dict):
                return {k: convert_datetime(v) for k, v in obj.items()}
            elif isinstance(obj, list):
                return [convert_datetime(item) for item in obj]
            return obj
        
        results = convert_datetime(results)
        
        with open(filepath, 'w', encoding='utf-8') as f:
            json.dump(results, f, ensure_ascii=False, indent=2)
    
    def load_optimization_results(self, filepath: str) -> Dict:
        """加载优化结果"""
        with open(filepath, 'r', encoding='utf-8') as f:
            results = json.load(f)
        
        # 将列表转换回DataFrame
        if 'replenishment_plan' in results:
            results['replenishment_plan'] = pd.DataFrame(results['replenishment_plan'])
        
        return results